package com.coszero.utils.utils

import android.util.Base64
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.security.DigestInputStream
import java.security.InvalidKeyException
import java.security.Key
import java.security.KeyFactory
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.Locale
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.Mac
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

/**
 * <pre>
 * author: Blankj
 * blog  : http://blankj.com
 * time  : 2016/08/02
 * desc  : 加密工具类
</pre> *
 * @version 1
 */
class EncryptUtils private constructor() {
    init {
        throw UnsupportedOperationException("u can't instantiate me...")
    }

    companion object {
        ///////////////////////////////////////////////////////////////////////////
        // hash encryption
        ///////////////////////////////////////////////////////////////////////////
        /**
         * Return the hex string of MD2 encryption.
         *
         * @param data The data.
         * @return the hex string of MD2 encryption
         */
        fun encryptMD2ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptMD2ToString(data.toByteArray())
        }

        /**
         * Return the hex string of MD2 encryption.
         *
         * @param data The data.
         * @return the hex string of MD2 encryption
         */
        fun encryptMD2ToString(data: ByteArray?): String {
            return bytes2HexString(encryptMD2(data))
        }

        /**
         * Return the bytes of MD2 encryption.
         *
         * @param data The data.
         * @return the bytes of MD2 encryption
         */
        fun encryptMD2(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "MD2")
        }

        /**
         * Return the hex string of MD5 encryption.
         *
         * @param data The data.
         * @return the hex string of MD5 encryption
         */
        fun encryptMD5ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptMD5ToString(data.toByteArray())
        }

        /**
         * Return the hex string of MD5 encryption.
         *
         * @param data The data.
         * @param salt The salt.
         * @return the hex string of MD5 encryption
         */
        fun encryptMD5ToString(data: String?, salt: String?): String {
            if (data == null && salt == null) return ""
            if (salt == null) return bytes2HexString(
                encryptMD5(
                    data!!.toByteArray()
                )
            )
            if (data == null) return bytes2HexString(encryptMD5(salt.toByteArray()))
            return bytes2HexString(encryptMD5((data + salt).toByteArray()))
        }

        /**
         * Return the hex string of MD5 encryption.
         *
         * @param data The data.
         * @return the hex string of MD5 encryption
         */
        fun encryptMD5ToString(data: ByteArray?): String {
            return bytes2HexString(encryptMD5(data))
        }

        /**
         * Return the hex string of MD5 encryption.
         *
         * @param data The data.
         * @param salt The salt.
         * @return the hex string of MD5 encryption
         */
        fun encryptMD5ToString(data: ByteArray?, salt: ByteArray?): String {
            if (data == null && salt == null) return ""
            if (salt == null) return bytes2HexString(encryptMD5(data))
            if (data == null) return bytes2HexString(encryptMD5(salt))
            val dataSalt: ByteArray = ByteArray(data.size + salt.size)
            System.arraycopy(data, 0, dataSalt, 0, data.size)
            System.arraycopy(salt, 0, dataSalt, data.size, salt.size)
            return bytes2HexString(encryptMD5(dataSalt))
        }

        /**
         * Return the bytes of MD5 encryption.
         *
         * @param data The data.
         * @return the bytes of MD5 encryption
         */
        fun encryptMD5(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "MD5")
        }

        /**
         * Return the hex string of file's MD5 encryption.
         *
         * @param filePath The path of file.
         * @return the hex string of file's MD5 encryption
         */
        fun encryptMD5File2String(filePath: String?): String {
            val file: File? = if (isSpace(filePath)) null else File(filePath)
            return encryptMD5File2String(file)
        }

        /**
         * Return the bytes of file's MD5 encryption.
         *
         * @param filePath The path of file.
         * @return the bytes of file's MD5 encryption
         */
        fun encryptMD5File(filePath: String?): ByteArray? {
            val file: File? = if (isSpace(filePath)) null else File(filePath)
            return encryptMD5File(file)
        }

        /**
         * Return the hex string of file's MD5 encryption.
         *
         * @param file The file.
         * @return the hex string of file's MD5 encryption
         */
        fun encryptMD5File2String(file: File?): String {
            return bytes2HexString(encryptMD5File(file))
        }

        /**
         * Return the bytes of file's MD5 encryption.
         *
         * @param file The file.
         * @return the bytes of file's MD5 encryption
         */
        fun encryptMD5File(file: File?): ByteArray? {
            if (file == null) return null
            var fis: FileInputStream? = null
            val digestInputStream: DigestInputStream
            try {
                fis = FileInputStream(file)
                var md: MessageDigest = MessageDigest.getInstance("MD5")
                digestInputStream = DigestInputStream(fis, md)
                val buffer: ByteArray = ByteArray(256 * 1024)
                while (true) {
                    if (digestInputStream.read(buffer) <= 0) break
                }
                md = digestInputStream.getMessageDigest()
                return md.digest()
            } catch (e: NoSuchAlgorithmException) {
                e.printStackTrace()
                return null
            } catch (e: IOException) {
                e.printStackTrace()
                return null
            } finally {
                try {
                    if (fis != null) {
                        fis.close()
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }

        /**
         * Return the hex string of SHA1 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA1 encryption
         */
        fun encryptSHA1ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptSHA1ToString(data.toByteArray())
        }

        /**
         * Return the hex string of SHA1 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA1 encryption
         */
        fun encryptSHA1ToString(data: ByteArray?): String {
            return bytes2HexString(encryptSHA1(data))
        }

        /**
         * Return the bytes of SHA1 encryption.
         *
         * @param data The data.
         * @return the bytes of SHA1 encryption
         */
        fun encryptSHA1(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "SHA1")
        }

        /**
         * Return the hex string of SHA224 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA224 encryption
         */
        fun encryptSHA224ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptSHA224ToString(data.toByteArray())
        }

        /**
         * Return the hex string of SHA224 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA224 encryption
         */
        fun encryptSHA224ToString(data: ByteArray?): String {
            return bytes2HexString(encryptSHA224(data))
        }

        /**
         * Return the bytes of SHA224 encryption.
         *
         * @param data The data.
         * @return the bytes of SHA224 encryption
         */
        fun encryptSHA224(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "SHA224")
        }

        /**
         * Return the hex string of SHA256 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA256 encryption
         */
        fun encryptSHA256ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptSHA256ToString(data.toByteArray())
        }

        /**
         * Return the hex string of SHA256 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA256 encryption
         */
        fun encryptSHA256ToString(data: ByteArray?): String {
            return bytes2HexString(encryptSHA256(data))
        }

        /**
         * Return the bytes of SHA256 encryption.
         *
         * @param data The data.
         * @return the bytes of SHA256 encryption
         */
        fun encryptSHA256(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "SHA256")
        }

        /**
         * Return the hex string of SHA384 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA384 encryption
         */
        fun encryptSHA384ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptSHA384ToString(data.toByteArray())
        }

        /**
         * Return the hex string of SHA384 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA384 encryption
         */
        fun encryptSHA384ToString(data: ByteArray?): String {
            return bytes2HexString(encryptSHA384(data))
        }

        /**
         * Return the bytes of SHA384 encryption.
         *
         * @param data The data.
         * @return the bytes of SHA384 encryption
         */
        fun encryptSHA384(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "SHA384")
        }

        /**
         * Return the hex string of SHA512 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA512 encryption
         */
        fun encryptSHA512ToString(data: String?): String {
            if (data == null || data.length == 0) return ""
            return encryptSHA512ToString(data.toByteArray())
        }

        /**
         * Return the hex string of SHA512 encryption.
         *
         * @param data The data.
         * @return the hex string of SHA512 encryption
         */
        fun encryptSHA512ToString(data: ByteArray?): String {
            return bytes2HexString(encryptSHA512(data))
        }

        /**
         * Return the bytes of SHA512 encryption.
         *
         * @param data The data.
         * @return the bytes of SHA512 encryption
         */
        fun encryptSHA512(data: ByteArray?): ByteArray? {
            return hashTemplate(data, "SHA512")
        }

        /**
         * Return the bytes of hash encryption.
         *
         * @param data The data.
         * @param algorithm The name of hash encryption.
         * @return the bytes of hash encryption
         */
        private fun hashTemplate(data: ByteArray?, algorithm: String): ByteArray? {
            if (data == null || data.size <= 0) return null
            try {
                val md: MessageDigest = MessageDigest.getInstance(algorithm)
                md.update(data)
                return md.digest()
            } catch (e: NoSuchAlgorithmException) {
                e.printStackTrace()
                return null
            }
        }

        ///////////////////////////////////////////////////////////////////////////
        // hmac encryption
        ///////////////////////////////////////////////////////////////////////////
        /**
         * Return the hex string of HmacMD5 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacMD5 encryption
         */
        fun encryptHmacMD5ToString(data: String?, key: String?): String {
            if (data == null || data.length == 0 || key == null || key.length == 0) return ""
            return encryptHmacMD5ToString(data.toByteArray(), key.toByteArray())
        }

        /**
         * Return the hex string of HmacMD5 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacMD5 encryption
         */
        fun encryptHmacMD5ToString(data: ByteArray?, key: ByteArray?): String {
            return bytes2HexString(encryptHmacMD5(data, key))
        }

        /**
         * Return the bytes of HmacMD5 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the bytes of HmacMD5 encryption
         */
        fun encryptHmacMD5(data: ByteArray?, key: ByteArray?): ByteArray? {
            return hmacTemplate(data, key, "HmacMD5")
        }

        /**
         * Return the hex string of HmacSHA1 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA1 encryption
         */
        fun encryptHmacSHA1ToString(data: String?, key: String?): String {
            if (data == null || data.length == 0 || key == null || key.length == 0) return ""
            return encryptHmacSHA1ToString(data.toByteArray(), key.toByteArray())
        }

        /**
         * Return the hex string of HmacSHA1 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA1 encryption
         */
        fun encryptHmacSHA1ToString(data: ByteArray?, key: ByteArray?): String {
            return bytes2HexString(encryptHmacSHA1(data, key))
        }

        /**
         * Return the bytes of HmacSHA1 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the bytes of HmacSHA1 encryption
         */
        fun encryptHmacSHA1(data: ByteArray?, key: ByteArray?): ByteArray? {
            return hmacTemplate(data, key, "HmacSHA1")
        }

        /**
         * Return the hex string of HmacSHA224 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA224 encryption
         */
        fun encryptHmacSHA224ToString(data: String?, key: String?): String {
            if (data == null || data.length == 0 || key == null || key.length == 0) return ""
            return encryptHmacSHA224ToString(data.toByteArray(), key.toByteArray())
        }

        /**
         * Return the hex string of HmacSHA224 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA224 encryption
         */
        fun encryptHmacSHA224ToString(data: ByteArray?, key: ByteArray?): String {
            return bytes2HexString(encryptHmacSHA224(data, key))
        }

        /**
         * Return the bytes of HmacSHA224 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the bytes of HmacSHA224 encryption
         */
        fun encryptHmacSHA224(data: ByteArray?, key: ByteArray?): ByteArray? {
            return hmacTemplate(data, key, "HmacSHA224")
        }

        /**
         * Return the hex string of HmacSHA256 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA256 encryption
         */
        fun encryptHmacSHA256ToString(data: String?, key: String?): String {
            if (data == null || data.length == 0 || key == null || key.length == 0) return ""
            return encryptHmacSHA256ToString(data.toByteArray(), key.toByteArray())
        }

        /**
         * Return the hex string of HmacSHA256 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA256 encryption
         */
        fun encryptHmacSHA256ToString(data: ByteArray?, key: ByteArray?): String {
            return bytes2HexString(encryptHmacSHA256(data, key))
        }

        /**
         * Return the bytes of HmacSHA256 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the bytes of HmacSHA256 encryption
         */
        fun encryptHmacSHA256(data: ByteArray?, key: ByteArray?): ByteArray? {
            return hmacTemplate(data, key, "HmacSHA256")
        }

        /**
         * Return the hex string of HmacSHA384 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA384 encryption
         */
        fun encryptHmacSHA384ToString(data: String?, key: String?): String {
            if (data == null || data.length == 0 || key == null || key.length == 0) return ""
            return encryptHmacSHA384ToString(data.toByteArray(), key.toByteArray())
        }

        /**
         * Return the hex string of HmacSHA384 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA384 encryption
         */
        fun encryptHmacSHA384ToString(data: ByteArray?, key: ByteArray?): String {
            return bytes2HexString(encryptHmacSHA384(data, key))
        }

        /**
         * Return the bytes of HmacSHA384 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the bytes of HmacSHA384 encryption
         */
        fun encryptHmacSHA384(data: ByteArray?, key: ByteArray?): ByteArray? {
            return hmacTemplate(data, key, "HmacSHA384")
        }

        /**
         * Return the hex string of HmacSHA512 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA512 encryption
         */
        fun encryptHmacSHA512ToString(data: String?, key: String?): String {
            if (data == null || data.length == 0 || key == null || key.length == 0) return ""
            return encryptHmacSHA512ToString(data.toByteArray(), key.toByteArray())
        }

        /**
         * Return the hex string of HmacSHA512 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the hex string of HmacSHA512 encryption
         */
        fun encryptHmacSHA512ToString(data: ByteArray?, key: ByteArray?): String {
            return bytes2HexString(encryptHmacSHA512(data, key))
        }

        /**
         * Return the bytes of HmacSHA512 encryption.
         *
         * @param data The data.
         * @param key The key.
         * @return the bytes of HmacSHA512 encryption
         */
        fun encryptHmacSHA512(data: ByteArray?, key: ByteArray?): ByteArray? {
            return hmacTemplate(data, key, "HmacSHA512")
        }

        /**
         * Return the bytes of hmac encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param algorithm The name of hmac encryption.
         * @return the bytes of hmac encryption
         */
        private fun hmacTemplate(
            data: ByteArray?,
            key: ByteArray?,
            algorithm: String
        ): ByteArray? {
            if (data == null || data.size == 0 || key == null || key.size == 0) return null
            try {
                val secretKey: SecretKeySpec = SecretKeySpec(key, algorithm)
                val mac: Mac = Mac.getInstance(algorithm)
                mac.init(secretKey)
                return mac.doFinal(data)
            } catch (e: InvalidKeyException) {
                e.printStackTrace()
                return null
            } catch (e: NoSuchAlgorithmException) {
                e.printStackTrace()
                return null
            }
        }

        ///////////////////////////////////////////////////////////////////////////
        // DES encryption
        ///////////////////////////////////////////////////////////////////////////
        /**
         * Return the Base64-encode bytes of DES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the Base64-encode bytes of DES encryption
         */
        fun encryptDES2Base64(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray {
            return base64Encode(encryptDES(data, key, transformation, iv))
        }

        /**
         * Return the hex string of DES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the hex string of DES encryption
         */
        fun encryptDES2HexString(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): String {
            return bytes2HexString(encryptDES(data, key, transformation, iv))
        }

        /**
         * Return the bytes of DES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of DES encryption
         */
        fun encryptDES(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return symmetricTemplate(data, key, "DES", transformation, iv, true)
        }

        /**
         * Return the bytes of DES decryption for Base64-encode bytes.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of DES decryption for Base64-encode bytes
         */
        fun decryptBase64DES(
            data: ByteArray,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return decryptDES(base64Decode(data), key, transformation, iv)
        }

        /**
         * Return the bytes of DES decryption for hex string.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of DES decryption for hex string
         */
        fun decryptHexStringDES(
            data: String,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return decryptDES(hexString2Bytes(data), key, transformation, iv)
        }

        /**
         * Return the bytes of DES decryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of DES decryption
         */
        fun decryptDES(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return symmetricTemplate(data, key, "DES", transformation, iv, false)
        }

        ///////////////////////////////////////////////////////////////////////////
        // 3DES encryption
        ///////////////////////////////////////////////////////////////////////////
        /**
         * Return the Base64-encode bytes of 3DES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the Base64-encode bytes of 3DES encryption
         */
        fun encrypt3DES2Base64(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray {
            return base64Encode(encrypt3DES(data, key, transformation, iv))
        }

        /**
         * Return the hex string of 3DES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the hex string of 3DES encryption
         */
        fun encrypt3DES2HexString(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): String {
            return bytes2HexString(encrypt3DES(data, key, transformation, iv))
        }

        /**
         * Return the bytes of 3DES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of 3DES encryption
         */
        fun encrypt3DES(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return symmetricTemplate(data, key, "DESede", transformation, iv, true)
        }

        /**
         * Return the bytes of 3DES decryption for Base64-encode bytes.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of 3DES decryption for Base64-encode bytes
         */
        fun decryptBase64_3DES(
            data: ByteArray,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return decrypt3DES(base64Decode(data), key, transformation, iv)
        }

        /**
         * Return the bytes of 3DES decryption for hex string.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of 3DES decryption for hex string
         */
        fun decryptHexString3DES(
            data: String,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return decrypt3DES(hexString2Bytes(data), key, transformation, iv)
        }

        /**
         * Return the bytes of 3DES decryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of 3DES decryption
         */
        fun decrypt3DES(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return symmetricTemplate(data, key, "DESede", transformation, iv, false)
        }

        ///////////////////////////////////////////////////////////////////////////
        // AES encryption
        ///////////////////////////////////////////////////////////////////////////
        /**
         * Return the Base64-encode bytes of AES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the Base64-encode bytes of AES encryption
         */
        fun encryptAES2Base64(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray {
            return base64Encode(encryptAES(data, key, transformation, iv))
        }

        /**
         * Return the hex string of AES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the hex string of AES encryption
         */
        fun encryptAES2HexString(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): String {
            return bytes2HexString(encryptAES(data, key, transformation, iv))
        }

        /**
         * Return the bytes of AES encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of AES encryption
         */
        fun encryptAES(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return symmetricTemplate(data, key, "AES", transformation, iv, true)
        }

        /**
         * Return the bytes of AES decryption for Base64-encode bytes.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of AES decryption for Base64-encode bytes
         */
        fun decryptBase64AES(
            data: ByteArray,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return decryptAES(base64Decode(data), key, transformation, iv)
        }

        /**
         * Return the bytes of AES decryption for hex string.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of AES decryption for hex string
         */
        fun decryptHexStringAES(
            data: String,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return decryptAES(hexString2Bytes(data), key, transformation, iv)
        }

        /**
         * Return the bytes of AES decryption.
         *
         * @param data The data.
         * @param key The key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param iv The buffer with the IV. The contents of the
         * buffer are copied to protect against subsequent modification.
         * @return the bytes of AES decryption
         */
        fun decryptAES(
            data: ByteArray?,
            key: ByteArray?,
            transformation: String?,
            iv: ByteArray?
        ): ByteArray? {
            return symmetricTemplate(data, key, "AES", transformation, iv, false)
        }

        /**
         * Return the bytes of symmetric encryption or decryption.
         *
         * @param data The data.
         * @param key The key.
         * @param algorithm The name of algorithm.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS5Padding*.
         * @param isEncrypt True to encrypt, false otherwise.
         * @return the bytes of symmetric encryption or decryption
         */
        private fun symmetricTemplate(
            data: ByteArray?,
            key: ByteArray?,
            algorithm: String,
            transformation: String?,
            iv: ByteArray?,
            isEncrypt: Boolean
        ): ByteArray? {
            if (data == null || data.size == 0 || key == null || key.size == 0) return null
            try {
                val keySpec: SecretKeySpec = SecretKeySpec(key, algorithm)
                val cipher: Cipher = Cipher.getInstance(transformation)
                if (iv == null || iv.size == 0) {
                    cipher.init(
                        if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE,
                        keySpec
                    )
                } else {
                    val params: AlgorithmParameterSpec = IvParameterSpec(iv)
                    cipher.init(
                        if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE,
                        keySpec,
                        params
                    )
                }
                return cipher.doFinal(data)
            } catch (e: Throwable) {
                e.printStackTrace()
                return null
            }
        }

        ///////////////////////////////////////////////////////////////////////////
        // RSA encryption
        ///////////////////////////////////////////////////////////////////////////
        /**
         * Return the Base64-encode bytes of RSA encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *RSA/CBC/PKCS1Padding*.
         * @return the Base64-encode bytes of RSA encryption
         */
        fun encryptRSA2Base64(
            data: ByteArray?,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?
        ): ByteArray {
            return base64Encode(encryptRSA(data, key, isPublicKey, transformation))
        }

        /**
         * Return the hex string of RSA encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *RSA/CBC/PKCS1Padding*.
         * @return the hex string of RSA encryption
         */
        fun encryptRSA2HexString(
            data: ByteArray?,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?
        ): String {
            return bytes2HexString(encryptRSA(data, key, isPublicKey, transformation))
        }

        /**
         * Return the bytes of RSA encryption.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *RSA/CBC/PKCS1Padding*.
         * @return the bytes of RSA encryption
         */
        fun encryptRSA(
            data: ByteArray?,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?
        ): ByteArray? {
            return rsaTemplate(data, key, isPublicKey, transformation, true)
        }

        /**
         * Return the bytes of RSA decryption for Base64-encode bytes.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *RSA/CBC/PKCS1Padding*.
         * @return the bytes of RSA decryption for Base64-encode bytes
         */
        fun decryptBase64RSA(
            data: ByteArray,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?
        ): ByteArray? {
            return decryptRSA(base64Decode(data), key, isPublicKey, transformation)
        }

        /**
         * Return the bytes of RSA decryption for hex string.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *RSA/CBC/PKCS1Padding*.
         * @return the bytes of RSA decryption for hex string
         */
        fun decryptHexStringRSA(
            data: String,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?
        ): ByteArray? {
            return decryptRSA(hexString2Bytes(data), key, isPublicKey, transformation)
        }

        /**
         * Return the bytes of RSA decryption.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *RSA/CBC/PKCS1Padding*.
         * @return the bytes of RSA decryption
         */
        fun decryptRSA(
            data: ByteArray?,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?
        ): ByteArray? {
            return rsaTemplate(data, key, isPublicKey, transformation, false)
        }

        /**
         * Return the bytes of RSA encryption or decryption.
         *
         * @param data The data.
         * @param key The key.
         * @param isPublicKey True to use public key, false to use private key.
         * @param transformation The name of the transformation, e.g., *DES/CBC/PKCS1Padding*.
         * @param isEncrypt True to encrypt, false otherwise.
         * @return the bytes of RSA encryption or decryption
         */
        private fun rsaTemplate(
            data: ByteArray?,
            key: ByteArray?,
            isPublicKey: Boolean,
            transformation: String?,
            isEncrypt: Boolean
        ): ByteArray? {
            if (data == null || data.size == 0 || key == null || key.size == 0) {
                return null
            }
            try {
                val rsaKey: Key?
                if (isPublicKey) {
                    val keySpec: X509EncodedKeySpec = X509EncodedKeySpec(key)
                    rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec)
                } else {
                    val keySpec: PKCS8EncodedKeySpec = PKCS8EncodedKeySpec(key)
                    rsaKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec)
                }
                if (rsaKey == null) return null
                val cipher: Cipher = Cipher.getInstance(transformation)
                cipher.init(if (isEncrypt) Cipher.ENCRYPT_MODE else Cipher.DECRYPT_MODE, rsaKey)
                val len: Int = data.size
                val maxLen: Int = if (isEncrypt) 117 else 128
                val count: Int = len / maxLen
                if (count > 0) {
                    var ret: ByteArray = ByteArray(0)
                    var buff: ByteArray = ByteArray(maxLen)
                    var index: Int = 0
                    for (i in 0 until count) {
                        System.arraycopy(data, index, buff, 0, maxLen)
                        ret = joins(ret, cipher.doFinal(buff))
                        index += maxLen
                    }
                    if (index != len) {
                        val restLen: Int = len - index
                        buff = ByteArray(restLen)
                        System.arraycopy(data, index, buff, 0, restLen)
                        ret = joins(ret, cipher.doFinal(buff))
                    }
                    return ret
                } else {
                    return cipher.doFinal(data)
                }
            } catch (e: NoSuchAlgorithmException) {
                e.printStackTrace()
            } catch (e: NoSuchPaddingException) {
                e.printStackTrace()
            } catch (e: InvalidKeyException) {
                e.printStackTrace()
            } catch (e: BadPaddingException) {
                e.printStackTrace()
            } catch (e: IllegalBlockSizeException) {
                e.printStackTrace()
            } catch (e: InvalidKeySpecException) {
                e.printStackTrace()
            }
            return null
        }

        private fun joins(prefix: ByteArray, suffix: ByteArray): ByteArray {
            val ret: ByteArray = ByteArray(prefix.size + suffix.size)
            System.arraycopy(prefix, 0, ret, 0, prefix.size)
            System.arraycopy(suffix, 0, ret, prefix.size, suffix.size)
            return ret
        }

        private val HEX_DIGITS: CharArray = charArrayOf(
            '0',
            '1',
            '2',
            '3',
            '4',
            '5',
            '6',
            '7',
            '8',
            '9',
            'A',
            'B',
            'C',
            'D',
            'E',
            'F'
        )

        private fun bytes2HexString(bytes: ByteArray?): String {
            if (bytes == null) return ""
            val len: Int = bytes.size
            if (len <= 0) return ""
            val ret: CharArray = CharArray(len shl 1)
            var i: Int = 0
            var j: Int = 0
            while (i < len) {
                ret.get(j++) = HEX_DIGITS.get(bytes.get(i).toInt() ushr 4 and 0x0f)
                ret.get(j++) = HEX_DIGITS.get(bytes.get(i).toInt() and 0x0f)
                i++
            }
            return String(ret)
        }

        private fun hexString2Bytes(hexString: String): ByteArray? {
            var hexString: String = hexString
            if (isSpace(hexString)) return null
            var len: Int = hexString.length
            if (len % 2 != 0) {
                hexString = "0" + hexString
                len = len + 1
            }
            val hexBytes: CharArray = hexString.uppercase(Locale.getDefault()).toCharArray()
            val ret: ByteArray = ByteArray(len shr 1)
            var i: Int = 0
            while (i < len) {
                ret.get(i shr 1) =
                    (hex2Dec(hexBytes.get(i)) shl 4 or hex2Dec(hexBytes.get(i + 1))).toByte()
                i += 2
            }
            return ret
        }

        private fun hex2Dec(hexChar: Char): Int {
            if (hexChar >= '0' && hexChar <= '9') {
                return hexChar.code - '0'.code
            } else if (hexChar >= 'A' && hexChar <= 'F') {
                return hexChar.code - 'A'.code + 10
            } else {
                throw IllegalArgumentException()
            }
        }

        private fun base64Encode(input: ByteArray?): ByteArray {
            return Base64.encode(input, Base64.NO_WRAP)
        }

        private fun base64Decode(input: ByteArray): ByteArray {
            return Base64.decode(input, Base64.NO_WRAP)
        }

        private fun isSpace(s: String?): Boolean {
            if (s == null) return true
            var i: Int = 0
            val len: Int = s.length
            while (i < len) {
                if (!Character.isWhitespace(s.get(i))) {
                    return false
                }
                ++i
            }
            return true
        }
    }
}
